#include "file.h"
#include "errors.h"

eFileMode::Mode::Mode (const TCHAR* szMode)
{
  flags = Invalid;

  // Keep track of explicit (r)ead/(w)rite/(a)ppend assignments.
  bool read   = false;
  bool write  = false;
  bool append = false;

  if (_tcsstr (szMode, _T ("r"))) {
    flags = Read;
    read  = true;
  }

  else if (_tcsstr (szMode, _T ("w"))) {
    flags = Write | Create | Truncate;
    write = true;
  }

  else if (_tcsstr (szMode, _T ("a")))
    flags = (Append | Create);

  if (_tcsstr (szMode, _T ("+"))) {
    if (read)
      flags |= Write;
    if (write || append)
      flags |= Read;
  }

  if (_tcsstr (szMode, _T ("b")))
    flags |= Binary;

  if (_tcsstr (szMode, _T ("S")))
    flags |= Sequential;
}


bool
eDiskFile::lock (eFileLockMode::Lock lock_mode)
{
  return false;

  /*
  bool success = false;
  int  iRet    = -1;

  if (! fh)
    return success;

  if (lock_mode & Unlock)
    iRet = flock (fh, LOCK_UN);
  else if (lock_mode & Exclusive)
    iRet = flock (fh, LOCK_EX);
  else if (lock_mode & Shared)
    iRet = flock (fh, LOCK_SH);

  success = (iRet != -1);

  return success;
  */
}


const TCHAR*
eFileMode_to_StdFileMode (eFileMode::Mode& mode, TCHAR* szMode)
{
  TCHAR* pszMode = szMode;

  if ((mode & eFileMode::Read) &&
      (mode & eFileMode::Write)) {
    if (mode & eFileMode::Truncate)
      *pszMode++ = _T ('w');
    else
      *pszMode++ = _T ('r');

    *pszMode++ = _T ('+');
  }

  else if (mode & eFileMode::Append) {
    *pszMode++ = _T ('a');

    if (mode & eFileMode::Read)
      *pszMode++ = _T ('+');
  }

  else if (mode & eFileMode::Read)
    *pszMode++ = _T ('r');

  else if (mode & eFileMode::Write)
    *pszMode++ = _T ('w');

  if (mode & eFileMode::Binary)
    *pszMode++ = _T ('b');

  if (mode & eFileMode::Sequential)
    *pszMode++ = _T ('S');


  // Handle situations where no eFileMode is set
  if (pszMode == szMode) {
    eSetLastError (EPSILON_ERROR_INVALID_FILE_MODE);
    //throw ("Invalid eFileMode!");
  }

  *pszMode++ = _T ('\0');

//  printf ("File Mode: %s (%06X)\n", szMode, (int)mode);

  return szMode;
}

bool
eDiskFile::open (eStringA& name, eFileMode::Mode file_mode)
{
  mode = file_mode;

  TCHAR szMode [8];
  eFileMode_to_StdFileMode (mode, szMode);

  fh = _tfopen (name.CString (), szMode);

  if (! fh)
    return false;

  if (mode & Epsilon::File::LockModes::Exclusive)
    lock (Epsilon::File::LockModes::Exclusive);

  return true;
}

bool
eDiskFile::close (void)
{
  if (fh) {
    if (mode & eFileMode::Exclusive)
      lock (eFileLockMode::Unlock);

    fclose (fh);

    fh   = NULL;
    mode = eFileMode::Invalid;

    return true;
  }

  return false;
}


bool
eDiskFile::seek (eFileOffset offset, eFileSeekOp::Operation seek_op)
{
  bool success = false;
  int  iRet    = -1;

  switch (seek_op) {
    case Epsilon::File::SeekOperations::Set:
      iRet = fseek (fh, offset, SEEK_SET);
      break;
    case Epsilon::File::SeekOperations::Current:
      iRet = fseek (fh, offset, SEEK_CUR);
      break;
    case Epsilon::File::SeekOperations::End:
      iRet = fseek (fh, offset, SEEK_END);
      break;
    default:
      eSetLastError (EPSILON_ERROR_INVALID_FILE_SEEK_OPERATION);
      //throw ("Invalid eFile seek operation.");
      break;
  }

  success = (iRet != -1);

  return success;
}

void
eDiskFile::rewind (void)
{
  ::rewind (fh);
}

eFileOffset
eDiskFile::tell (void)
{
  eFileOffset off = ftell (fh);

  return off;
}

eFileSize
eDiskFile::size (void)
{
  eFileOffset off = tell ();

  seek (0, eFileSeekOp::End);
  eFileSize size_ = (eFileSize) tell ();

  seek (off, eFileSeekOp::Set);

  return size_;
}


eFileSize
eDiskFile::read (void* pBuf, eFileSize element_size, int num_elements)
{
//  assert (pBuf != NULL);
//  assert (mode & eFileMode::Read);

  if (! pBuf) {
    eSetLastError (EPSILON_ERROR_INVALID_FILE_READ_BUFFER);
    return 0;
  }

  eFileSize elements = fread (pBuf, element_size, num_elements, fh);

  return elements;
}

eFileSize
eDiskFile::write (const void* pBuf, eFileSize element_size, int num_elements)
{
  // assert (pBuf != NULL);
  // assert (mode & eFileMode::Write);

  if (! pBuf) {
    eSetLastError (EPSILON_ERROR_INVALID_DATA);
    return 0;
  }

  eFileSize elements = fwrite (pBuf, element_size, num_elements, fh);

  return elements;
}

//#define FilePerm(Read,Write,Exec) (0x4 * Read) | (0x2 * Write) | (0x1 * Exec)



#ifdef WIN32

bool
eMemoryMappedFile::lock (eFileLockMode::Lock lock_mode)
{
  return false;

  /*
  bool success = false;
  int  iRet    = -1;

  if (! fh)
    return success;

  if (lock_mode & Unlock)
    iRet = flock (fh, LOCK_UN);
  else if (lock_mode & Exclusive)
    iRet = flock (fh, LOCK_EX);
  else if (lock_mode & Shared)
    iRet = flock (fh, LOCK_SH);

  success = (iRet != -1);

  return success;
  */
}


// Copys a Unicode string to an ANSI buffer.
void strcpy_utoa (char* szOut, const TCHAR* szIn)
{
#ifdef _UNICODE
  size_t len    = _tcslen (szIn);
  char*  pszOut = (char *)szOut;
  char*  pszIn  = (char *)szIn;

  // Remove the extra NULL byte between each ANSI char.
  for (size_t i = 0; i < len; i++) {
    *pszOut++ = *pszIn++;
    ++pszIn;
  }
  *pszOut++ = '\0';
#else
  strcpy (szOut, szIn);
#endif
}

#include "debug.h"

bool
eMemoryMappedFile::open (eStringA& name, eFileMode::Mode file_mode)
{
  mode = file_mode;

  if (! (mode & Epsilon::File::Modes::Write)) {
    fh = CreateFile (name.CString (), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
  } else {
    eMessageBox (_T ("Writable memory mapped files aren't supported yet."));
    return false;
  }

  if (! fh) {
    eMessageBox (_T ("Oh no!"));
    return false;
  }

  file_size = GetFileSize (fh, NULL);

  // Win32 Memory Mapped Files cannot have spaces in their handle names.
  TCHAR szMappedName [MAX_PATH];

  for (unsigned int i = 0; i < name.GetLength (); i++) {
    //if (szName [i] == _T (' '))
      //szMappedName [i] = _T ('_');
    //else
      szMappedName [i] = name.CString () [i];
  }

  mapped_file = CreateFileMapping (fh, NULL, PAGE_READONLY, 0, 0, szMappedName);
  view_base   = MapViewOfFile     (mapped_file, FILE_MAP_READ, 0, 0, file_size);

  seek (0, eFileSeekOp::Set);

  /*
  if (mode & Epsilon::File::LockModes::Exclusive)
    lock (Epsilon::File::LockModes::Exclusive);
  */

  return true;
}

bool
eMemoryMappedFile::close (void)
{
  if (fh) {
  /*
    if (mode & eFileMode::Exclusive)
      lock (eFileLockMode::Unlock);
    */

    UnmapViewOfFile (view_base);

    CloseHandle (mapped_file);
    CloseHandle (fh);
    //_lclose (fh);
    fh = NULL;

    mode = eFileMode::Invalid;

    return true;
  }

  return false;
}


bool
eMemoryMappedFile::seek (eFileOffset offset, eFileSeekOp::Operation seek_op)
{
  bool success = false;
  int  iRet    = 0;

  switch (seek_op) {
    case Epsilon::File::SeekOperations::Set:
      pos = offset;
      //iRet = fseek (fh, offset, SEEK_SET);
      break;
    case Epsilon::File::SeekOperations::Current:
      pos += offset;
      //iRet = fseek (fh, offset, SEEK_CUR);
      break;
    case Epsilon::File::SeekOperations::End:
      pos = (eFileOffset)(size () - offset);
      //iRet = fseek (fh, offset, SEEK_END);
      break;
    default:
      iRet = -1;
      eSetLastError (EPSILON_ERROR_INVALID_FILE_SEEK_OPERATION);
      //throw ("Invalid eFile seek operation.");
      break;
  }

  success = (iRet != -1);

  return success;
}

void
eMemoryMappedFile::rewind (void)
{
  pos = 0;
  ////::rewind (fh);
}

eFileOffset
eMemoryMappedFile::tell (void)
{
  eFileOffset off = pos;
  //eFileOffset off = 0;//ftell (fh);

  return pos;
}

eFileSize
eMemoryMappedFile::size (void)
{
  /*
  eFileOffset off = tell ();

  seek (0, eFileSeekOp::End);
  eFileSize size_ = (eFileSize) tell ();

  seek (off, eFileSeekOp::Set);
  */

  eFileSize size_ = file_size;

  return size_;
}


eFileSize
eMemoryMappedFile::read (void* pBuf, eFileSize element_size, int num_elements)
{
//  assert (pBuf != NULL);
//  assert (mode & eFileMode::Read);

  if (! pBuf) {
    eSetLastError (EPSILON_ERROR_INVALID_FILE_READ_BUFFER);
    return 0;
  }

  eFileSize elements = 0;

  if (view_base != NULL) {
    memcpy (pBuf, (char *)view_base + pos, element_size * num_elements);
    elements = num_elements;
  }

  pos += (eFileOffset)(element_size * num_elements);

  return elements;
}

eFileSize
eMemoryMappedFile::write (const void* pBuf, eFileSize element_size, int num_elements)
{
  // assert (pBuf != NULL);
  // assert (mode & eFileMode::Write);

  if (! pBuf) {
    eSetLastError (EPSILON_ERROR_INVALID_DATA);
    return 0;
  }

  eFileSize elements = 0;//fwrite (pBuf, element_size, num_elements, fh);

  return elements;
}

#endif
